home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 June: Reference Library / Dev.CD Jun 96 RL / Dev.CD Jun 96 RL.toast / Technical Documentation / develop / develop Issue 24 / develop Issue 24 code / Scriptable Database 1.0a15 / Database / GroupControlObject.cp < prev    next >
Encoding:
Text File  |  1996-04-25  |  30.5 KB  |  797 lines  |  [TEXT/CWIE]

  1. //================================================================================
  2. // Greg Anderson
  3. // db+
  4. //
  5. // Group control object
  6. // 17 May 1994
  7. // 31 Dec 1994
  8. //================================================================================
  9.  
  10. #include "GroupControlObject.h"
  11.  
  12. #include "DatabaseDocument.h"
  13. #include "AbstractRecord.h"
  14.  
  15. #include "Exceptions.h"
  16.  
  17. //
  18. // For CopyMemory
  19. //
  20. #include "AbstractData.h"
  21.  
  22. //--------------------------------------------------------------------------------
  23. // TGroupControlObject::~TGroupControlObject
  24. //--------------------------------------------------------------------------------
  25. TGroupControlObject::~TGroupControlObject()
  26. {
  27. } // TGroupControlObject::~TGroupControlObject
  28.  
  29. //--------------------------------------------------------------------------------
  30. // TGroupControlObject::InitializeRecordCursors
  31. //--------------------------------------------------------------------------------
  32. void TGroupControlObject::InitializeRecordCursors()
  33. {
  34.     for(short i=0; i<kRecordsPerGroup; ++i)
  35.         fRecordCursors[i] = nil;
  36. } // TGroupControlObject::InitializeRecordCursors
  37.  
  38. //--------------------------------------------------------------------------------
  39. // TGroupControlObject::DocumentKeySpace
  40. //--------------------------------------------------------------------------------
  41. Int64 TGroupControlObject::DocumentKeySpace()
  42. {
  43.     return DBDocument()->ObjectsKeySpace();
  44. } // TGroupControlObject::DocumentKeySpace
  45.  
  46. //--------------------------------------------------------------------------------
  47. // TGroupControlObject::CalculateRelativeIndex
  48. //--------------------------------------------------------------------------------
  49. long TGroupControlObject::CalculateRelativeIndex(long recordIndex) const
  50. {
  51.     long relativeIndex = recordIndex - this->FirstRecordIndex();
  52.     
  53.     if((relativeIndex < 0) || (relativeIndex >= kRecordsPerGroup))
  54.         Throw(eIndexOutOfRange);
  55.     
  56.     return relativeIndex;
  57. } // TGroupControlObject::CalculateRelativeIndex
  58.  
  59. //--------------------------------------------------------------------------------
  60. // TGroupControlObject::NextRecordIndex
  61. //--------------------------------------------------------------------------------
  62. long TGroupControlObject::NextRecordIndex(long recordIndex) const
  63. {
  64.     long flags = (this->RecordData(recordIndex))[kRecordIDFlagsWord];
  65.     long nextIndex = recordIndex;
  66.     
  67.     //
  68.     // If the data record indicator bits != 0, then this record
  69.     // is a DB record; every DB record is one entry long, so
  70.     // their size is always kLongWordsPerRecord.
  71.     //
  72.     if((flags & kDataRecordIndicatorBits) != 0)
  73.         ++nextIndex;
  74.     //
  75.     // Data records are variable size, though.  The physical
  76.     // size is always some multiple of kLongWordsPerRecord, and
  77.     // is encoded in the kDataRecordPhysicalSizeBits.
  78.     //
  79.     else
  80.     {
  81.         nextIndex += ((flags & kDataRecordPhysicalSizeBits) >> (kDataRecordPhysicalSizeShift)) + 1;
  82.     }
  83.     
  84.     //
  85.     // If we get to the end of the group, return -1
  86.     //
  87.     if(nextIndex >= (this->FirstRecordIndex() + kRecordsPerGroup))
  88.     {
  89.         ASSERT(nextIndex == this->FirstRecordIndex() + kRecordsPerGroup);
  90.         nextIndex = -1;
  91.     }
  92.     
  93.     return nextIndex;
  94. } // TGroupControlObject::NextRecordIndex
  95.  
  96. //--------------------------------------------------------------------------------
  97. // TGroupControlObject::PreviousRecordIndex
  98. //--------------------------------------------------------------------------------
  99. long TGroupControlObject::PreviousRecordIndex(long recordIndex) const
  100. {
  101.     long previousIndex = recordIndex;
  102.  
  103.     //
  104.     // If we're already at the beginning, there's no point
  105.     // in doing any further calculations.
  106.     //
  107.     if(recordIndex <= this->FirstRecordIndex())
  108.     {
  109.         ASSERT(recordIndex == this->FirstRecordIndex());
  110.         previousIndex = -1;
  111.     }
  112.     else
  113.     {
  114.         long flags = (this->RecordData(recordIndex))[kRecordIDFlagsWord];
  115.         
  116.         //
  117.         // If the data record indicator bits != 0, then this record
  118.         // is a DB record; every DB record is one entry long, so
  119.         // their size is always kLongWordsPerRecord.
  120.         //
  121.         if((flags & kDataRecordIndicatorBits) != 0)
  122.             --previousIndex;
  123.         //
  124.         // Data records are variable size, though.  The physical
  125.         // size is always some multiple of kLongWordsPerRecord, and
  126.         // is encoded in the kDataRecordPhysicalSizeBits.
  127.         //
  128.         else
  129.         {
  130.             previousIndex -= (((flags & kPreviousRecordPhysicalSizeBits) >> (kPreviousRecordPhysicalSizeShift)) + 1);
  131.         }
  132.  
  133.         ASSERT(previousIndex >= this->FirstRecordIndex());
  134.     }
  135.     
  136.     return previousIndex;
  137. } // TGroupControlObject::PreviousRecordIndex
  138.  
  139. //--------------------------------------------------------------------------------
  140. // TGroupControlObject::BlockEncodedPhysicalSize
  141. //--------------------------------------------------------------------------------
  142. long TGroupControlObject::BlockEncodedPhysicalSize(long recordIndex) const
  143. {
  144.     long flags = (this->RecordData(recordIndex))[kRecordIDFlagsWord];
  145.  
  146.     if((flags & kDataRecordIndicatorBits) != 0)
  147.         return 0;
  148.     else
  149.         return ((flags & kDataRecordPhysicalSizeBits) >> (kDataRecordPhysicalSizeShift));
  150. } // TGroupControlObject::BlockEncodedPhysicalSize
  151.  
  152. //--------------------------------------------------------------------------------
  153. // TGroupControlObject::SetBlockEncodedPhysicalSize
  154. //--------------------------------------------------------------------------------
  155. void TGroupControlObject::SetBlockEncodedPhysicalSize(long recordIndex, long newEncodedSize)
  156. {
  157.     long flags = (this->RecordData(recordIndex))[kRecordIDFlagsWord];
  158.  
  159.     Require((this->BlockEncodedPhysicalSize(recordIndex) > 0) && (newEncodedSize > 0));
  160.  
  161.     long newFlags = (flags & ~kDataRecordPhysicalSizeBits) | ((newEncodedSize << kDataRecordPhysicalSizeShift) & kDataRecordPhysicalSizeBits);
  162.     this->WriteRecordWord(recordIndex, kRecordIDFlagsWord, newFlags);
  163. } // TGroupControlObject::SetBlockEncodedPhysicalSize
  164.  
  165. //--------------------------------------------------------------------------------
  166. // TGroupControlObject::LongwordsInRecord
  167. //--------------------------------------------------------------------------------
  168. long TGroupControlObject::LongwordsInRecord(long recordIndex) const
  169. {
  170.     return (this->BlockEncodedPhysicalSize(recordIndex) << kLongWordsPerRecordShift) + kLongWordsPerRecord;
  171. } // TGroupControlObject::LongwordsInRecord
  172.  
  173. //--------------------------------------------------------------------------------
  174. // TGroupControlObject::CacheCreatedCursor
  175. //--------------------------------------------------------------------------------
  176. void TGroupControlObject::CacheCreatedCursor(long recordIndex, TAbstractRecord* cursor)
  177. {
  178.     REQUIREVALIDPOINTER(cursor);
  179.     long relativeIndex = this->CalculateRelativeIndex(recordIndex);
  180.     if(fRecordCursors[relativeIndex] != nil)
  181.     {
  182.         if(fRecordCursors[relativeIndex]->HasReference() == false)
  183.             delete fRecordCursors[relativeIndex];
  184.     }
  185.     fRecordCursors[relativeIndex] = cursor;
  186. } // TGroupControlObject::CacheCreatedCursor
  187.  
  188. //--------------------------------------------------------------------------------
  189. // TGroupControlObject::NotifyCursorReleased
  190. //--------------------------------------------------------------------------------
  191. void TGroupControlObject::NotifyCursorReleased(const TAbstractRecord* cursor)
  192. {
  193.     REQUIREVALIDPOINTER(cursor);
  194.     long recordIndex = cursor->RecordIndex();
  195.     long relativeIndex = this->CalculateRelativeIndex(recordIndex);
  196.     
  197.     //
  198.     // The first case to worry about is when a new item is created,
  199.     // but the transaction is backed out before the new item becomes
  200.     // a part of the database.  In that event, we are in danger of
  201.     // caching an object that represents a free node--and the object
  202.     // may have the wrong class the next time it's referenced.  To avoid
  203.     // this problem, we delete free nodes as soon as their references
  204.     // go away.
  205.     //
  206.     if(fRecordCursors[relativeIndex] == cursor)
  207.     {
  208.         //
  209.         // 
  210.         //
  211.         if(cursor->ThisRecordIsFree(nil) && (cursor->InTransaction() == false))
  212.         {
  213.             fRecordCursors[relativeIndex] = nil;
  214.             delete (TAbstractRecord*)cursor;
  215.         }
  216.     }
  217.     //
  218.     // The other case to worry about it when an item is freed,
  219.     // someone else may come along and allocate a new record
  220.     // on top of it before all of the old references go away.
  221.     // In that case, the cursor won't be cached in the record
  222.     // cursors cache table any more, and we will wait for
  223.     // the references to go away before we delete the item.
  224.     //
  225.     else
  226.     {
  227.         delete (TAbstractRecord*)cursor;
  228.     }
  229. } // TGroupControlObject::NotifyCursorReleased
  230.  
  231. //--------------------------------------------------------------------------------
  232. // TGroupControlObject::GetRecord
  233. //--------------------------------------------------------------------------------
  234. TAbstractRecord* TGroupControlObject::GetRecord(long recordIndex)
  235. {
  236.     long relativeIndex = this->CalculateRelativeIndex(recordIndex);
  237.     TAbstractRecord* cursor = nil;
  238.     
  239.     //
  240.     // Look up the cursor in the table of cached pointers that we keep.
  241.     // If the cursor doesn't exist yet, then ask the document to create it.
  242.     //
  243.     cursor = fRecordCursors[relativeIndex];
  244.     if(cursor == nil)
  245.     {
  246.         //
  247.         // n.b. MakeRecord is going to fill in the
  248.         // apropriate entry in fRecordCursors.
  249.         //
  250.         cursor = DBDocument()->MakeRecord(recordIndex, this->GetRecordWord(recordIndex, 0));
  251.     }
  252.         
  253.     return cursor;
  254. } // TGroupControlObject::GetRecord
  255.  
  256. //--------------------------------------------------------------------------------
  257. // TGroupControlObject::RecordData
  258. //--------------------------------------------------------------------------------
  259. long* TGroupControlObject::RecordData(long recordIndex) const
  260. {
  261.     long relativeIndex = this->CalculateRelativeIndex(recordIndex);
  262.     
  263.     if(fRecordData == nil)
  264.     {
  265.         //
  266.         // Const cast away deliberately; we want to provide an
  267.         // interface where 'RecordData' claims to be const, but
  268.         // it may need to call 'ReadRecordGroupFromDisk', which
  269.         // is not const.
  270.         //
  271.         TGroupControlObject* me = (TGroupControlObject*)this;
  272.         me->ReadRecordGroupFromDisk();
  273.     }
  274.     
  275.     return (fRecordData) + (relativeIndex * kLongWordsPerRecord);
  276. } // TGroupControlObject::RecordData
  277.      
  278. //--------------------------------------------------------------------------------
  279. // TGroupControlObject::GetRecordWord
  280. //--------------------------------------------------------------------------------
  281. long TGroupControlObject::GetRecordWord(long recordIndex, long longwordNumber) const
  282. {
  283.     long theData = 0;
  284.     
  285.     if((longwordNumber >= 0) && (longwordNumber < this->LongwordsInRecord(recordIndex)))
  286.     {
  287.         theData = (this->RecordData(recordIndex))[longwordNumber];
  288.     }
  289.     else
  290.         Throw(eIndexOutOfRange);
  291.     
  292.     return theData;
  293. } // TGroupControlObject::GetRecordWord
  294.      
  295. //--------------------------------------------------------------------------------
  296. // TGroupControlObject::WriteRecordWord
  297. //
  298. // This is a private method not to be called by any of the group control object's
  299. // friends.  Any external client of the group control object that wishes to change
  300. // the contents of a record must make a copy of the record with
  301. // 'MakeRecordDataCopy', then write the entire record back with 'ChangeRecordData'
  302. //--------------------------------------------------------------------------------
  303. void TGroupControlObject::WriteRecordWord(long recordIndex, long longwordNumber, long theData)
  304. {    
  305.     if((longwordNumber >= 0) && (longwordNumber < this->LongwordsInRecord(recordIndex)))
  306.     {
  307.         (this->RecordData(recordIndex))[longwordNumber] = theData;
  308.         fRecordGroupHasChanged = true;
  309.     }
  310.     else
  311.         Throw(eIndexOutOfRange);
  312. } // TGroupControlObject::WriteRecordWord
  313.  
  314. //--------------------------------------------------------------------------------
  315. // TGroupControlObject::WriteThroughToTransaction
  316. //
  317. // This routine changes bits in a data record both in the persistant (group
  318. // control object stored) structure and the change image (stored in the update
  319. // pointer of some transaction).  This is currently only necessary in one
  320. // instance: setting the 'previous block's physical size' field of a data
  321. // object when splitting a block apart or joining two blocks.  The split is
  322. // done independantly of this transaction, so we need to make sure that the
  323. // appropriate bits are changed in both places so that the next/previous
  324. // block links do not become invalid when the transaction is committed.
  325. //--------------------------------------------------------------------------------
  326. void TGroupControlObject::WriteThroughToTransaction(long recordIndex, long longwordNumber, long theData, long theMask)
  327. {
  328.     long currentWordValue = this->GetRecordWord(recordIndex, longwordNumber);
  329.     this->WriteRecordWord(recordIndex, longwordNumber, (currentWordValue & ~theMask) | (theData & theMask));
  330.  
  331.     //
  332.     // If the update cursor exists, inform it that it needs to change some bits
  333.     //
  334.     long relativeIndex = this->CalculateRelativeIndex(recordIndex);
  335.     if(fRecordCursors[relativeIndex] != nil)
  336.     {
  337.         fRecordCursors[relativeIndex]->WriteThroughToTransaction(longwordNumber, theData, theMask);
  338.     }
  339. }
  340.  
  341. //--------------------------------------------------------------------------------
  342. // TGroupControlObject::MakeRecordDataCopy
  343. //--------------------------------------------------------------------------------
  344. long* TGroupControlObject::MakeRecordDataCopy(long recordIndex)
  345. {
  346.     long* recordDataAddress = this->RecordData(recordIndex);
  347.     long recordDataLongs = this->LongwordsInRecord(recordIndex);
  348.     
  349.     long* dataCopy = new long[recordDataLongs];
  350.     FailNil(dataCopy);
  351.     CopyMemory(recordDataAddress, dataCopy, recordDataLongs * sizeof(long)); // memcpy(dataCopy, recordDataAddress, recordDataLongs * sizeof(long));
  352.     
  353.     return dataCopy;
  354. } // TGroupControlObject::MakeRecordDataCopy
  355.  
  356. //--------------------------------------------------------------------------------
  357. // TGroupControlObject::ChangeRecordData
  358. //--------------------------------------------------------------------------------
  359. void TGroupControlObject::ChangeRecordData(long recordIndex, long* newData, Boolean inhibitCompare /* = false */)
  360. {
  361.     REQUIREVALIDPOINTER(newData);
  362.     long* recordDataAddress = this->RecordData(recordIndex);
  363.     long lengthToCopy = this->LongwordsInRecord(recordIndex) * sizeof(long);
  364.  
  365.     //
  366.     // If the data has changed in any way, then copy the
  367.     // new data back into the group control object and
  368.     // mark that this object has changed.
  369.     //
  370.     // It is worth it to do the compare if it means that
  371.     // we might not need to write this group back to disk;
  372.     // if, however, this group is already dirty, or if
  373.     // the caller knows for certain that the data has
  374.     // changed, then we won't bother to call memcmp.
  375.     //
  376.     // Future:  When we start collecting changed record
  377.     // groups into change sets, then it also matters if
  378.     // this record group belongs to the current change set
  379.     // yet, as we would like to avoid merging change sets
  380.     // if the record data hasn't actually changed.
  381.     //
  382.     // ◊disable memory compare for now
  383.     //
  384.     if((inhibitCompare == true) || (this->RecordGroupNeedsSave()) /* || (memcmp(recordDataAddress, newData, lengthToCopy) != 0) */ )
  385.     {
  386.         CopyMemory(newData, recordDataAddress, lengthToCopy); // memcpy(recordDataAddress, newData, lengthToCopy);
  387.         this->RecordGroupHasChanged();
  388.     }
  389. } // TGroupControlObject::ChangeRecordData
  390.  
  391. //--------------------------------------------------------------------------------
  392. // TGroupControlObject::RecordDataReference
  393. //--------------------------------------------------------------------------------
  394. const TConstDataReference TGroupControlObject::RecordDataReference(long recordIndex, long dataType, long longwordNumber, long numberOfBytes) const
  395. {
  396.     long* recordDataAddress = this->RecordData(recordIndex);
  397.  
  398.     return TConstDataReference(dataType, (char*)&recordDataAddress[longwordNumber], numberOfBytes);
  399. } // TGroupControlObject::RecordDataReference
  400.  
  401. //--------------------------------------------------------------------------------
  402. // TGroupControlObject::TrimBlock
  403. //
  404. // Reduce the size of 'blockToTrim' down to 'newEncodedPhysicalSize'.
  405. // Return the index of the block left over, if any.
  406. //--------------------------------------------------------------------------------
  407. long TGroupControlObject::TrimBlock(long blockToTrim, long newEncodedPhysicalSize)
  408. {
  409.     long currentEncodedPhysicalSize = this->BlockEncodedPhysicalSize(blockToTrim);
  410.     long leftOverPiece = -1;
  411.     
  412.     //
  413.     // Don't trim a block down by just one record size; we
  414.     // always want the leftover peice to be at least 2 records long.
  415.     //
  416.     if((currentEncodedPhysicalSize - newEncodedPhysicalSize) > 1)
  417.     {
  418.         //
  419.         // We are going to make our block smaller by a few (at least two)
  420.         // records; 'encodedSizeLeftAfterTrim' is the size of the
  421.         // record left over after we do so.
  422.         //
  423.         long encodedSizeLeftAfterTrim = (currentEncodedPhysicalSize - newEncodedPhysicalSize) - 1;
  424.         
  425.         //
  426.         // Calculate the record index of the block we're splitting
  427.         // off ('leftOverPiece') and the record index of the block
  428.         // after this one (we'll need to adjust its 'physical size
  429.         // of previous block') link.
  430.         //
  431.         leftOverPiece = blockToTrim + newEncodedPhysicalSize + 1;
  432.         long blockToFixUp = blockToTrim + currentEncodedPhysicalSize + 1;
  433.         ASSERT(leftOverPiece + encodedSizeLeftAfterTrim + 1 == blockToFixUp);
  434.         Require(blockToFixUp <= this->FirstRecordIndex() + kRecordsPerGroup);
  435.         
  436.         //
  437.         // We only split blocks that are free, so we know that we
  438.         // can adjust 'blockToTrim' and 'leftOverPiece' without
  439.         // worrying about a transaction.
  440.         //
  441.         Require((fRecordCursors[this->CalculateRelativeIndex(blockToTrim)] == nil) && (fRecordCursors[this->CalculateRelativeIndex(leftOverPiece)] == nil));
  442.         long trimBlockFlags = this->GetRecordWord(blockToTrim, kRecordIDFlagsWord);
  443.         trimBlockFlags = (trimBlockFlags & ~kDataRecordPhysicalSizeBits) | ((newEncodedPhysicalSize << kDataRecordPhysicalSizeShift) & kDataRecordPhysicalSizeBits);
  444.         long leftOverBlockFlags =    (kFreeDataRecordID) |
  445.                                     ((encodedSizeLeftAfterTrim << kDataRecordPhysicalSizeShift) & kDataRecordPhysicalSizeBits) |
  446.                                     ((newEncodedPhysicalSize << kPreviousRecordPhysicalSizeShift) & kPreviousRecordPhysicalSizeBits);
  447.         this->WriteRecordWord(blockToTrim, kRecordIDFlagsWord, trimBlockFlags);
  448.         this->WriteRecordWord(leftOverPiece, kRecordIDFlagsWord, leftOverBlockFlags);
  449.         this->WriteRecordWord(leftOverPiece, kFreeNodeLinkByte, kFreeRecordNotLinkedToTree);
  450.         this->WriteRecordWord(leftOverPiece, kPreviousFreeNodeLinkByte, kNilIndex);
  451.         
  452.         //
  453.         // Don't forget, when setting the 'previous block physical size'
  454.         // bits, that the record being changed may have an active change image.
  455.         // If that's the case, then it will be necessary to change both
  456.         // the Group Control Object's previous-physical-size value, and the
  457.         // change image's previous-physical-size value.
  458.         //
  459.         if(blockToFixUp < (this->FirstRecordIndex() + kRecordsPerGroup))
  460.         {
  461.             this->WriteThroughToTransaction(blockToFixUp, kRecordIDFlagsWord, (encodedSizeLeftAfterTrim << kPreviousRecordPhysicalSizeShift), kPreviousRecordPhysicalSizeBits);
  462.         }
  463.     }
  464.     
  465.     return leftOverPiece;
  466. } // TGroupControlObject::TrimBlock
  467.  
  468. //--------------------------------------------------------------------------------
  469. // TGroupControlObject::Verify
  470. //--------------------------------------------------------------------------------
  471. void TGroupControlObject::Verify(long recordIndex, Boolean verifySubsequent)
  472. {
  473.     long nextRecord = this->NextRecordIndex(recordIndex);
  474.     long previousRecord = this->PreviousRecordIndex(recordIndex);
  475.     Boolean ourEncodedSizeIsZero = (this->BlockEncodedPhysicalSize(recordIndex) == 0);
  476.     
  477.     //
  478.     // PreviousRecord should only be -1 at the first record in the group
  479.     //
  480.     if(previousRecord == -1)
  481.     {
  482.         if(recordIndex != this->FirstRecordIndex())
  483.             DebugStr("\pRecord other than the first had a nil previous");
  484.     }
  485.     else
  486.     {
  487.         if(this->NextRecordIndex(previousRecord) != recordIndex)
  488.             DebugStr("\pRecord's previous link doesn't point back");
  489.  
  490.         Boolean prevEncodedSizeIsZero = (this->BlockEncodedPhysicalSize(previousRecord) == 0);
  491.         if(prevEncodedSizeIsZero != ourEncodedSizeIsZero)
  492.             DebugStr("\pMixed single and multiple record data blocks in one group");
  493.     }
  494.     
  495.     if(nextRecord != -1)
  496.     {
  497.         if(nextRecord > (FirstRecordIndex() + kRecordsPerGroup))
  498.             DebugStr("\pData record points past the end of a record group");
  499.         else if((nextRecord < this->FirstRecordIndex() + kRecordsPerGroup))
  500.         {
  501.             if(this->PreviousRecordIndex(nextRecord) != recordIndex)
  502.                 DebugStr("\pRecord's next link doesn't point back");
  503.             else if(verifySubsequent == true)
  504.                 this->Verify(nextRecord, true);
  505.         }
  506.     }
  507. } // TGroupControlObject::Verify
  508.  
  509. //--------------------------------------------------------------------------------
  510. // TGroupControlObject::AllocateMemoryForRecordData
  511. //--------------------------------------------------------------------------------
  512. void TGroupControlObject::AllocateMemoryForRecordData()
  513. {
  514.     if(fRecordData == nil)
  515.     {
  516.         fRecordData = new long[kLongWordsPerRecord * kRecordsPerGroup];
  517.         FailNil(fRecordData);
  518.     }
  519. } // TGroupControlObject::AllocateMemoryForRecordData
  520.  
  521. //--------------------------------------------------------------------------------
  522. // TGroupControlObject::ReadRecordGroupFromDisk
  523. //--------------------------------------------------------------------------------
  524. void TGroupControlObject::ReadRecordGroupFromDisk(TAbstractBackingStore* backingStoreToUse /*= nil*/)
  525. {
  526.     Require(fRecordGroupHasChanged == false);
  527.     if(fRecordData == nil)
  528.     {
  529.         this->AllocateMemoryForRecordData();
  530.         DBDocument()->ReadRecordRange(fRecordData, this->FirstRecordIndex() * kSingleRecordSize, kLongWordsPerRecord * kRecordsPerGroup * sizeof(long), backingStoreToUse);
  531.         fRecordGroupHasChanged = false;
  532.     }
  533. } // TGroupControlObject::ReadRecordGroupFromDisk
  534.  
  535. //--------------------------------------------------------------------------------
  536. // TGroupControlObject::FlushChangesToDisk
  537. //--------------------------------------------------------------------------------
  538. void TGroupControlObject::FlushChangesToDisk(TAbstractBackingStore* backingStoreToUse /*= nil*/)
  539. {
  540.     if((fRecordData != nil) && fRecordGroupHasChanged)
  541.         DBDocument()->WriteRecordRange(fRecordData, this->FirstRecordIndex() * kSingleRecordSize, kLongWordsPerRecord * kRecordsPerGroup * sizeof(long), backingStoreToUse);
  542.     fRecordGroupHasChanged = false;
  543. } // TGroupControlObject::FlushChangesToDisk
  544.  
  545. //--------------------------------------------------------------------------------
  546. // TGroupControlObject::PurgeRecordGroupFromMemory
  547. //--------------------------------------------------------------------------------
  548. void TGroupControlObject::PurgeRecordGroupFromMemory()
  549. {
  550.     //
  551.     // NEVER purge if we cannot flush!  (Should have a better error number, perhaps)
  552.     //
  553.     if(this->DBDocument()->CanSaveDocument())
  554.         FailErr(-1);
  555.     
  556.     if(fRecordGroupHasChanged)
  557.         this->FlushChangesToDisk();
  558.     delete [] fRecordData;
  559.     fRecordData = nil;
  560. } // TGroupControlObject::PurgeRecordGroupFromMemory
  561.  
  562. //--------------------------------------------------------------------------------
  563. // TGroupControlObject::PurgeIfUnreferenced
  564. //--------------------------------------------------------------------------------
  565. void TGroupControlObject::PurgeIfUnreferenced()
  566. {
  567. #if 0
  568.     Boolean referenced = false;
  569.     long i;
  570.     
  571.     for(i=0;i<kRecordsPerGroup;++i)
  572.     {
  573.         if((fRecordCursors[i] != nil) && (fRecordCursors[i]->HasReference()))
  574.         {
  575.             referenced = true;
  576.             break;
  577.         }
  578.     }
  579.     
  580.     if(referenced == false)
  581.     {
  582.         for(i=0;i<kRecordsPerGroup;++i)
  583.         {
  584.             if(fRecordCursors[i] != nil)
  585.             {
  586.                 delete fRecordCursors[i];
  587.                 fRecordCursors[i] = nil;
  588.             }
  589.         }
  590.         
  591.         INHERITED::PurgeIfUnreferenced();
  592.     }
  593. #endif
  594. } // TGroupControlObject::PurgeIfUnreferenced
  595.  
  596. //--------------------------------------------------------------------------------
  597. // TGroupControlObject::InitializeNewGroup
  598. //
  599. // This routine makes a bunch (64) of new database records.  Most (63) of
  600. // these are immediately pushed onto the db record free list.  One of the
  601. // new records is returned, unlinked and ready to use.
  602. //--------------------------------------------------------------------------------
  603. long TGroupControlObject::InitializeNewGroup(long nextFreeNode, long& oneFreeIndex)
  604. {
  605.     this->AllocateMemoryForRecordData();
  606.     
  607.     //
  608.     // Make kRecordsPerGroup free DB records
  609.     //
  610.     for(long i=this->FirstRecordIndex() + kRecordsPerGroup - 1;i>= this->FirstRecordIndex();--i)
  611.     {
  612.         long* data = this->RecordData(i);
  613.         data[kFreeNodeIDByte] = kFreeDBRecordID;
  614.         data[kFreeNodeLinkByte] = nextFreeNode;
  615.         nextFreeNode = i;
  616.     }
  617.     
  618.     //
  619.     // Mark the first record as not being attached to the free list
  620.     //
  621.     this->RecordData(this->FirstRecordIndex())[kFreeNodeLinkByte] = kFreeRecordNotLinkedToTree;
  622.     
  623.     //
  624.     // The first record in the group is not linked to the free list.
  625.     // The second record in the group is returned as the new first free record.
  626.     //
  627.     oneFreeIndex = this->FirstRecordIndex();
  628.     return this->FirstRecordIndex() + 1;
  629. } // TGroupControlObject::InitializeNewGroup
  630.  
  631. //--------------------------------------------------------------------------------
  632. // TGroupControlObject::InitializeNewDataGroup
  633. //
  634. // This routine makes one or two free data records.  Both are returned unlinked
  635. // to the tree.  One is always of size 'desiredEncodedPhysicalSize'; the other
  636. // is however big it needs to be to fill out the rest of the record group.
  637. //--------------------------------------------------------------------------------
  638. long TGroupControlObject::InitializeNewDataGroup(long desiredEncodedPhysicalSize, long& leftOverBlock)
  639. {
  640.     //
  641.     // Make one 4K data record
  642.     //
  643.     this->AllocateMemoryForRecordData();
  644.     long* data = this->RecordData(this->FirstRecordIndex());
  645.     data[kFreeNodeIDByte] = kFreeDataRecordID + (63L << kDataRecordPhysicalSizeShift);
  646.     data[kFreeNodeLinkByte] = kFreeRecordNotLinkedToTree;
  647.     data[kPreviousFreeNodeLinkByte] = kNilIndex;
  648.     
  649.     long oneFreeIndex = this->FirstRecordIndex();
  650.     leftOverBlock = this->TrimBlock(oneFreeIndex, desiredEncodedPhysicalSize);
  651.  
  652.     return oneFreeIndex;
  653. } // TGroupControlObject::InitializeNewDataGroup
  654.  
  655. //--------------------------------------------------------------------------------
  656. // TGroupControlObject::IndexIsFree
  657. //--------------------------------------------------------------------------------
  658. Boolean TGroupControlObject::IndexIsFree(long recordIndex) const
  659. {
  660.     return ((this->GetRecordWord(recordIndex, kFreeNodeIDByte) & kFreeRecordIDBits) == kFreeRecordIDBits);
  661. } // TGroupControlObject::IndexIsFree
  662.  
  663. //--------------------------------------------------------------------------------
  664. // TGroupControlObject::IndexIsFreeAndOnFreeList
  665. //--------------------------------------------------------------------------------
  666. Boolean TGroupControlObject::IndexIsFreeAndOnFreeList(long recordIndex) const
  667. {
  668.     if(this->IndexIsFree(recordIndex))
  669.         return this->GetRecordWord(recordIndex, kFreeNodeLinkByte) != kFreeRecordNotLinkedToTree;
  670.     else
  671.         return false;
  672. } // TGroupControlObject::IndexIsFreeAndOnFreeList
  673.  
  674. //--------------------------------------------------------------------------------
  675. // TGroupControlObject::NextFreeIndex
  676. //--------------------------------------------------------------------------------
  677. long TGroupControlObject::NextFreeIndex(long afterWhichIndex) const
  678. {
  679.     //
  680.     // Test to see if 'afterWhichIndex' really is a free node.
  681.     //
  682.     Require(this->IndexIsFree(afterWhichIndex));
  683.     // Require(fRecordCursors[this->CalculateRelativeIndex(afterWhichIndex)] == nil);
  684.  
  685.     //
  686.     // Get the next free index.  It might be nice to do some sanity
  687.     // checking on it, too, but we won't do that because the next
  688.     // free node might be in some other group control object.
  689.     //
  690.     long theNextFreeIndex = this->GetRecordWord(afterWhichIndex, kFreeNodeLinkByte);
  691.     if(theNextFreeIndex == kFreeRecordNotLinkedToTree)
  692.         theNextFreeIndex = kNilIndex;
  693.     
  694.     return theNextFreeIndex;
  695. } // TGroupControlObject::NextFreeIndex
  696.  
  697. //--------------------------------------------------------------------------------
  698. // TGroupControlObject::PreviousFreeIndex
  699. //--------------------------------------------------------------------------------
  700. long TGroupControlObject::PreviousFreeIndex(long beforeWhichIndex) const
  701. {
  702.     //
  703.     // Test to see if 'beforeWhichIndex' really is a free node.
  704.     //
  705.     Require(this->IndexIsFree(beforeWhichIndex));
  706.     // Require(fRecordCursors[this->CalculateRelativeIndex(beforeWhichIndex)] == nil);
  707.  
  708.     //
  709.     // The first free list is unique in that it does not maintain
  710.     // previous links.
  711.     //
  712.     long freeList = this->FreeListToUse(beforeWhichIndex);
  713.     Require(freeList > 0);
  714.     
  715.     //
  716.     // Get the next free index.  It might be nice to do some sanity
  717.     // checking on it, too, but we won't do that because the previous
  718.     // free node might be in some other group control object.
  719.     //
  720.     long thePreviousIndex = kNilIndex;
  721.     if(this->IndexIsFreeAndOnFreeList(beforeWhichIndex))
  722.         thePreviousIndex = this->GetRecordWord(beforeWhichIndex, kPreviousFreeNodeLinkByte);
  723.     
  724.     return thePreviousIndex;
  725. } // TGroupControlObject::PreviousFreeIndex
  726.  
  727. //--------------------------------------------------------------------------------
  728. // TGroupControlObject::RecordCursorStale
  729. //--------------------------------------------------------------------------------
  730. void TGroupControlObject::RecordCursorStale(long staleRecord)
  731. {
  732.     //
  733.     // Smash the reference to any cursor to the free node
  734.     //
  735.     long relativeIndex = this->CalculateRelativeIndex(staleRecord);
  736.     if(fRecordCursors[relativeIndex] != nil)
  737.     {
  738.         //
  739.         // Free the cursor if we can
  740.         //
  741.         TAbstractRecord* freeCursor = fRecordCursors[relativeIndex];
  742.         fRecordCursors[relativeIndex] = nil;
  743.         
  744.         //
  745.         // If the cursor is unreferenced, this routine
  746.         // will delete it directly.  If there are references,
  747.         // then the cursor will be marked for deletion
  748.         // as soon as the last reference is released.
  749.         //
  750.         freeCursor->DisposeRecordIfUnreferenced();
  751.     }
  752. } // TGroupControlObject::RecordCursorStale
  753.  
  754. //--------------------------------------------------------------------------------
  755. // TGroupControlObject::MergeFreeBlocks
  756. //--------------------------------------------------------------------------------
  757. long TGroupControlObject::MergeFreeBlocks(long firstRecord, long recordToMerge)
  758. {
  759.     long blockToFixUp = this->NextRecordIndex(recordToMerge);
  760.     ASSERT(recordToMerge == this->NextRecordIndex(firstRecord));
  761.     
  762.     //
  763.     // First, remove the records from their free lists, if they're on one
  764.     //
  765.     this->DBDocument()->RemoveFromFreeList(firstRecord);
  766.     this->DBDocument()->RemoveFromFreeList(recordToMerge);
  767.     
  768.     //
  769.     // Next, increase the physical size of 'firstRecord' to
  770.     // encompass 'recordToMerge'.
  771.     //
  772.     long encodedMergeSize = this->BlockEncodedPhysicalSize(recordToMerge);
  773.     long originalEncodedSize = this->BlockEncodedPhysicalSize(firstRecord);
  774.     Require((encodedMergeSize > 0) && (originalEncodedSize > 0));
  775.     long newEncodedSize = originalEncodedSize + encodedMergeSize + 1;
  776.     this->SetBlockEncodedPhysicalSize(firstRecord, newEncodedSize);
  777.     ASSERT(newEncodedSize == this->FreeListToUse(firstRecord));
  778.     
  779.     //
  780.     // Finally, adjust the block after 'recordToMerge' so
  781.     // that it has the correct previous physical size entry.
  782.     // (blockToFixUp will be kNilIndex if 'recordToMerge' is
  783.     // the last block in the group).
  784.     //
  785.     if(blockToFixUp != kNilIndex)
  786.     {
  787.         ASSERT(blockToFixUp == this->NextRecordIndex(firstRecord));
  788.         this->WriteThroughToTransaction(blockToFixUp, kRecordIDFlagsWord, (newEncodedSize << kPreviousRecordPhysicalSizeShift), kPreviousRecordPhysicalSizeBits);
  789.     }
  790.  
  791.     //
  792.     // Return the free list that the new record would be pushed onto.
  793.     //
  794.     return this->FreeListToUse(firstRecord);
  795. } // TGroupControlObject::MergeFreeBlocks
  796.  
  797.